Skip to content

fix: honor offset in non-virtual scrollTo, export scroll config types#57

Open
aojunhao123 wants to merge 7 commits into
react-component:masterfrom
aojunhao123:fix/non-virtual-scroll-offset
Open

fix: honor offset in non-virtual scrollTo, export scroll config types#57
aojunhao123 wants to merge 7 commits into
react-component:masterfrom
aojunhao123:fix/non-virtual-scroll-offset

Conversation

@aojunhao123

@aojunhao123 aojunhao123 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

scrollTo({ key, align, offset }) already applied offset in the virtual list, but the non-virtual (virtual={false}) RawList path ignored it. It set a --rc-listy-item-scroll-margin-top CSS variable that only accounted for the sticky-header height, never the user's offset, and it never set a bottom margin — so align: 'bottom' offsets did nothing at all in non-virtual mode.

This PR brings the non-virtual path to parity and tidies up the approach:

  • RawList now honors offset — it sets scrollMarginTop / scrollMarginBottom directly on the scroll target at scroll time (sticky-header height + offset for items with align: 'top', offset otherwise), matching the virtual list.
  • Drops the CSS-variable indirection (--rc-listy-item-scroll-margin-top) and the per-render inline scrollMarginTop on every item. The margin is now applied only on the actual target, and only while scrolling.
  • Exports ScrollAlign and ListyScrollToConfig from the package entry so consumers can type the config they pass to scrollTo (previously declared but not exported).
  • Adds a scroll-to demo that exercises both virtual and raw modes with live align / offset / key / group controls.

No changes to the public config shape — offset was already a typed option; it simply didn't work in non-virtual mode.

Test plan

  • tests/listy.behavior.test.tsx and tests/semantic.test.tsx updated and passing (25/25) — assert scrollMarginTop/scrollMarginBottom reflect header height + offset on the target, and that a user-supplied styles.item.scrollMarginTop is present at rest but the internal offset wins on the scroll target.
  • tsc --noEmit clean
  • eslint clean on changed source and the new demo
  • Manually verify the scroll-to demo in both virtual and non-virtual modes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 新功能
    • 新增“滚动到指定位置”演示页面与示例入口,支持虚拟滚动、top/bottom/auto 对齐、offset,以及按 item key/分组滚动展示。
    • 对外补充滚动对齐与配置类型(ScrollAlign、ListyScrollToConfig)。
  • 问题修复/改进
    • 优化粘性分组与条目的 scroll 边距计算与应用精度,正确处理 offset,确保顶部/底部/自动对齐更一致。
    • 调整虚拟列表粘性分组在 align: bottom 时的滚动落点逻辑。
  • 测试
    • 更新并扩展滚动边距相关用例断言,覆盖分组、sticky 与 virtual 场景。

@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

@aojunhao123 is attempting to deploy a commit to the React Component Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@aojunhao123, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 55 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 35cce2db-5dd4-4136-8dfc-6524bc508737

📥 Commits

Reviewing files that changed from the base of the PR and between 7b6970c and 0c69e3e.

📒 Files selected for processing (11)
  • .dumirc.ts
  • src/RawList/index.tsx
  • src/RawList/useRawListScroll.ts
  • src/VirtualList/index.tsx
  • src/VirtualList/useFlattenRows.ts
  • src/VirtualList/useStickyGroupHeader.tsx
  • src/hooks/useItemKey.ts
  • src/util.ts
  • tests/hooks.test.tsx
  • tests/listy.behavior.test.tsx
  • tests/semantic.test.tsx

Walkthrough

新增 scrollTo 交互式演示,调整 RawList 和 VirtualList 的滚动边距与对齐处理,公开滚动配置类型,并补充分组、sticky、virtual 与原生滚动测试。

Changes

滚动边距与 scrollTo 流程

Layer / File(s) Summary
滚动边距应用与类型导出
src/index.ts, src/RawList/index.tsx, src/RawList/useRawListScroll.ts, src/VirtualList/index.tsx
RawList 移除旧的分组滚动边距处理;滚动逻辑根据目标、对齐方式和 offset 设置 scrollMarginTopscrollMarginBottom;sticky 分组在非 bottom 对齐时应用组头偏移,并导出 ScrollAlignListyScrollToConfig
滚动边距行为验证
tests/listy.behavior.test.tsx, tests/semantic.test.tsx
测试分组、非分组、sticky、virtual 及原生滚动场景中的边距计算,并验证自定义条目样式保持不变。
scrollTo 交互式示例
docs/demos/scroll-to.md, docs/examples/scroll-to.tsx
新增文档页面和示例组件,支持 virtual、对齐方式、offset、条目 key 与分组 key 滚动操作。

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ListyRef
  participant useRawListScroll
  participant TargetElement
  participant Browser
  ListyRef->>useRawListScroll: scrollTo(config)
  useRawListScroll->>TargetElement: 设置 scrollMarginTop/Bottom
  useRawListScroll->>Browser: 调用 scrollIntoView
Loading

Possibly related PRs

Poem

兔子挥爪滚到底,
顶部底部都对齐。
offset 轻轻加一笔,
分组条目跳入溪。
新例闪闪,测试齐!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了核心改动:非虚拟列表的 scrollTo 现在支持 offset,并新增滚动配置类型导出。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (6fb43b7) to head (779a902).

Additional details and impacted files
@@            Coverage Diff            @@
##            master       #57   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            8        10    +2     
  Lines          211       211           
  Branches        62        59    -3     
=========================================
  Hits           211       211           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for custom offsets when scrolling to items or group headers in RawList. It refactors the scroll margin application by replacing the CSS-variable-based approach with direct inline styles (scrollMarginTop and scrollMarginBottom) dynamically computed on the target element. It also adds a new "Scroll To" demo, exports relevant types, and updates the test suite to verify the new behavior. There are no review comments, so I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/RawList/useRawListScroll.ts`:
- Around line 69-77: 在 useRawListScroll 中分离 item 与 group 的滚动定位属性:根据 isItem 选择
data-item-key 或 data-group-key 构造选择器,避免继续使用
data-key;同时确保对应的条目和分组节点分别写入匹配的数据属性,并保留现有 applyScrollMargin 的定位行为。
- Around line 39-52: Update applyScrollMargin so sticky header height is
included for item targets whenever align is not 'bottom', including 'auto';
retain zero header offset for bottom alignment and non-item targets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 41b08372-c439-473b-a4a2-4559168c7b23

📥 Commits

Reviewing files that changed from the base of the PR and between 13f952a and b83e38c.

📒 Files selected for processing (7)
  • docs/demos/scroll-to.md
  • docs/examples/scroll-to.tsx
  • src/RawList/index.tsx
  • src/RawList/useRawListScroll.ts
  • src/index.ts
  • tests/listy.behavior.test.tsx
  • tests/semantic.test.tsx

Comment thread src/RawList/useRawListScroll.ts Outdated
Comment thread src/RawList/useRawListScroll.ts Outdated
@zombieJ

zombieJ commented Jul 14, 2026

Copy link
Copy Markdown
Member

来自 Code Review Skill

这个 PR 在做什么

这个 PR 为 RawList 的 item/group scrollTo 补齐 offset,处理 sticky header 和 auto 对齐,并公开滚动配置类型。它还增加了完整演示以及 RawList、VirtualList、sticky、语义样式相关测试。

结论

不适合合并

存在一个可由合法输入触发的滚动目标混淆问题:item key 与 group key 相同时,会滚动到错误节点。应先拆分两类节点的定位属性并补充回归测试;此外,当前分支与 master 存在合并冲突,需要先更新分支。

问题清单

🔴 高优先级(阻塞合并)

  • item 与 group 共用 data-key,相同 key 会命中错误的滚动目标src/RawList/useRawListScroll.ts:72
    RawList 同时把 data-key 写在 group section 和 item 上;由于 group section 位于 item 之前,scrollTo({ key: value }) 在 item key 与 group key 字符串相同时会选中 group section。这样不仅滚动位置错误,sticky header offset 也会基于错误节点计算。建议分别使用 data-item-keydata-group-key,按配置类型构造选择器,并加入 key 冲突测试。

🟡 中优先级(建议修复)

无。

🟢 低优先级(可选改进)

无。

The virtual list already applied the `offset` from `scrollTo({ key, align,
offset })`, but the non-virtual RawList path ignored it: it set a
`--rc-listy-item-scroll-margin-top` CSS variable that only accounted for the
sticky header height and never the user's offset, and it never set a bottom
margin, so `align: 'bottom'` offsets did nothing.

- RawList now applies `scrollMarginTop`/`scrollMarginBottom` directly on the
  scroll target at scroll time (header height + offset for items on `top`
  align, offset otherwise), matching the virtual path.
- Drop the CSS-variable indirection and the per-render inline scrollMarginTop
  on every item; the margin is applied only on the target, only when scrolling.
- Export `ScrollAlign` and `ListyScrollToConfig` so consumers can type the
  config they pass to `scrollTo`.
- Add a `scroll-to` demo exercising both virtual and raw modes.
`align: 'auto'` maps to `scrollIntoView({ block: 'nearest' })`, which can land
the item at the container top where the sticky header would occlude it. The
header offset was only added for `align: 'top'`, so `auto` (and the default)
were left uncorrected in both the raw and virtual paths.

Apply the header offset whenever alignment is not explicitly `bottom`, in both
RawList and VirtualList, keeping the two modes consistent.
@aojunhao123 aojunhao123 force-pushed the fix/non-virtual-scroll-offset branch from 62adb9d to 7b6970c Compare July 14, 2026 03:38
@zombieJ

zombieJ commented Jul 14, 2026

Copy link
Copy Markdown
Member

来自 Code Review Skill

这个 PR 在做什么

PR #57 已 rebase 到包含 #56 的最新 master,合并冲突已经解决。它为非虚拟列表的 scrollTo 补齐 offset 支持,同时调整 sticky 分组的自动对齐逻辑、导出滚动配置类型,并增加演示与测试。

结论

不适合合并

RawList 的 offset 实现基本正确,但 VirtualList 对 align: 'auto' 的 sticky header 补偿会错误影响最终选择 bottom 对齐的情况。核心 CI 均通过;Vercel 仍只是团队授权失败。

问题清单

🔴 高优先级(阻塞合并)

  • auto 最终按底部对齐时仍错误叠加 sticky header 高度src/VirtualList/index.tsx:106
    rc-virtual-list 会根据目标位于视口上方还是下方,将 auto 动态解析为 top 或 bottom;但这里预先把 header 高度加入统一的 offset。当目标位于视口下方时,底部对齐也会多出一个 header 高度,造成错误的底部间距。新增测试只调用了 offset 函数,没有覆盖 auto → bottom 的实际落点;建议暂时只对显式 align: 'top' 补偿,或让补偿能够根据最终对齐方向决定。

🟡 中优先级(建议修复)

  • 调用 scrollTo 会永久覆盖用户传入的 scroll margin 样式src/RawList/useRawListScroll.ts:49
    直接修改 React 管理的 element.style.scrollMarginTop/Bottom 后没有恢复。若用户通过 styles.item 设置这些属性,调用一次 scrollTo 后其值会被永久替换;普通 rerender 也不一定恢复,因为 React 认为 style props 没有变化。建议在 scrollIntoView 前保存原值,并在浏览器读取滚动参数后恢复,或继续用内部 CSS 变量隔离状态。

🟢 低优先级(可选改进)

  • item/group 同名 key 仍会选错目标src/RawList/useRawListScroll.ts:72
    上次指出的问题仍存在。不过重新对比 master 后确认,这是已有实现中的历史问题,并非本 PR 引入,因此不再作为本次合并阻塞项;仍建议后续拆分 data-item-keydata-group-key

Item keys and group keys shared one namespace in virtual row keys and
raw-mode data-key attributes, so a colliding key (an item id equal to a
group key) made scrollTo target the wrong node. Row keys are now tagged
as `item:x` / `group:x` via a single toTaggedKey helper, stamped once
per row at flatten time — which also turns the per-frame itemKey calls
in the virtual hot path into plain property reads.

Also fixes two edges in the sticky-header offset path: the group lookup
map is keyed by tagged keys so a string/number key mismatch can no
longer scroll while silently dropping the header compensation, and a
falsy group key (0, '') no longer disables the header push-up.

Note: raw-mode data-key attribute values change from the raw key to the
tagged form (`data-key="item:1"`).
The scrollTo refactor split the single fallthrough into explicit
branches; the numeric-offset and position-config paths had no
virtual-mode coverage (only raw-mode).
…d align

Non-virtual (raw list) scrollTo wrote scroll-margin directly on the target and
left it there, permanently overwriting styles.item.scrollMargin*. React diffs
the style prop against its own last-rendered value, not the live DOM, so a
same-value rerender never restores it. Apply the margin only for the
synchronous scrollIntoView call and restore the element's inline value right
after.

Virtual scrollTo now reads the resolved `align` exposed by the virtual-list
offset callback instead of reverse-engineering the direction via
getScrollInfo(), adding the sticky-header offset only when the item lands at
the top.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants